package emby.lang.stdlib;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import emby.EnvironmentPaths;
import emby.err.EmbyError;
import emby.err.RuntimeError;
import emby.err.TypeError;
import emby.lang.EmbyList;
import emby.lang.EmbyNull;
import emby.lang.NativeFunction;
import emby.lang.Scope;
import emby.util.IO;

/**
 * Shell contains the native function implementations of the shell library
 * This includes functions such as cd, ls, rm, cp, etc. which provide shell-like file
 * management capabilities
 * <br>
 * Within emby, load these functions by executing run("shell")
 * @author Justin
 */
public class Shell {
	
	public static String makeAbsolute(String relativePath){
		String replacement = IO.addSlashIfNecessary(EnvironmentPaths.getWorkingDirectory());
		return makeAbsolute(replacement, relativePath);
	}
	
	/**
	 * If the relativePath has ".." sequences, t
	 * takes takes relative path and turns it into an absolute path by moving
	 * up one level from the working directory for each ".." found.
	 * @param relativePath
	 * @return an absolute path
	 */
	public static String makeAbsolute(String basepath, String relativePath){
		String[] tokens = (basepath+relativePath).split(IO.file_sep_escaped);
		Stack<String> s = new Stack<String>();
		for(String token : tokens){
			s.push(token);
		}
		List<String> finalResult = new ArrayList<String>(tokens.length);
		int freepops =0;
		while(!s.empty()){
			String top = s.peek();
			//System.out.println("top:"+top);
			if( top.equals(".")){
				//System.out.println("Case: .");
				s.pop();
			}else if (top.equals("..")){
				//System.out.println("Case: ..");
				s.pop();
				if( s.empty()){
					throw new RuntimeException("Path cannot be resolved: "+relativePath);
				}
				top = s.peek();
				if( top.equals("..")){
					freepops++;
				}else{
					s.pop();
				}
			}else if(freepops > 0){
				if( freepops>0){
					for(int i=0;i<freepops;i++){
						s.pop();
					}
					freepops=0;
				}
			}
			else{
				//System.out.println("Case: normal");
				finalResult.add(s.pop());
			}
		}
		StringBuilder sb = new StringBuilder();
		for( int i= finalResult.size()-1; i>=0; i--){
			sb.append(finalResult.get(i));
			if( i!=0){
				sb.append(IO.file_sep);
			}
		}
		return sb.toString();
	}
	
	private static String getParentPath(String filepath){
		return(new File( (new File(filepath)).getAbsolutePath())).getParent();
	}


	public final static NativeFunction pwd = new NativeFunction("pwd"){
		private static final long serialVersionUID = -4922864625652347638L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if( !NativeFunction.argsMatch(line, args)){
				typeError(line, args);
			}
			return EnvironmentPaths.getWorkingDirectory();
		}
	};

	public final static NativeFunction ls = new NativeFunction("ls"){
		private static final long serialVersionUID = -8495828442510682924L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			List<Object> filenames = null;
			if( args.length==0){ //if no args are given
				File folder = new File(EnvironmentPaths.getWorkingDirectory());
				filenames = Arrays.asList((Object[])folder.list());
			}else {
				//System.out.println("before: "+args[0]+" -> "+makeAbsolute((String) args[0])+" -> ");
				File folder = new File(EnvironmentPaths.attachWDIfNecc(makeAbsolute((String) args[0])));
				//System.out.println("checking: "+folder.getAbsolutePath());
				Object[] folders = folder.list();
				if( folders == null) folders = new Object[0];
				filenames = Arrays.asList(folders);
				for(int i = 1; i<args.length; i++){
					try{
						folder = new File(EnvironmentPaths.attachWDIfNecc(makeAbsolute((String) args[i])));
						//System.out.println("checking: "+folder.getAbsolutePath());
						String[] lst;
						if( ( lst = folder.list())!=null)
							filenames.addAll( Arrays.asList((Object[])lst));
					}catch(ClassCastException e){
						throw new TypeError(line, args[i]+" is not of class java.lang.String");
					}
				}
			}
			return new EmbyList(filenames);
		}
	};

	public final static NativeFunction cp = new NativeFunction("cp"){
		private static final long serialVersionUID = -4891514914422672931L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if(!NativeFunction.argsMatch(line, args, String.class, String.class)){
				typeError(line, args);
			}
			String originalFile = EnvironmentPaths.attachWDIfNecc(makeAbsolute(((String)args[0])));
			String destinationPath = EnvironmentPaths.attachWDIfNecc((makeAbsolute((String)args[1])));
			File fromFile = new File(originalFile);
			File toFile = new File(destinationPath);
			System.out.println("Copying "+originalFile+" to "+destinationPath);
			if(!fromFile.exists()){
				System.err.println("File "+originalFile+" does not exist");
			}
			if(toFile.exists()){// overwrite
				System.out.println("Overwriting "+destinationPath);
			}
			FileInputStream from = null;
			FileOutputStream to = null;
			try{
				from = new FileInputStream(fromFile);
				to = new FileOutputStream(toFile);
				byte[] buffer = new byte[4096];
				int bytesRead;
				while((bytesRead = from.read(buffer))!=-1){
					to.write(buffer, 0, bytesRead);//write
				}
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				if( from!=null){
					try{
						from.close();
					}catch(IOException e){
						e.printStackTrace();
					}
				}
				if( to!=null){
					try{
						to.close();
					}catch(IOException e){
						e.printStackTrace();
					}
				}
			}
			return EmbyNull.Null;
		}
	};

	public final static NativeFunction cd = new NativeFunction("cd"){
		private static final long serialVersionUID = -4922864629656347638L;
		@SuppressWarnings("deprecation")
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if( !NativeFunction.argsMatch(line, args, String.class)){
				typeError(line, args);
			}
			String dir = EnvironmentPaths.attachWDIfNecc(makeAbsolute((String) args[0]));
			EnvironmentPaths.cd(dir);
			return dir;
		}
	};

	public final static NativeFunction rm = new NativeFunction("rm"){
		private static final long serialVersionUID = -4922864629656347638L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if( !NativeFunction.argsMatch(line, args, String.class)){
				typeError(line, args);
			}
			String filename = EnvironmentPaths.attachWDIfNecc(makeAbsolute(((String)args[0])));
			File f = new File(filename);
			if( f.exists()){
				System.out.println("Deleting "+filename);
				boolean b = f.delete();
				if( !b){
					System.err.println("Access denied. File "+filename+" could not be deleted. Aborting");
				}
			}else{
				System.err.println("File "+filename+" does not exist. Aborting");
			}
			return EmbyNull.Null;
		}
	};

	public final static NativeFunction cat = new NativeFunction("cat"){
		private static final long serialVersionUID = -4922864629656347638L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			StringBuilder sb = new StringBuilder();
			for(int i = 0; i<args.length; i++){
				try{
					String filename = EnvironmentPaths.attachWDIfNecc(makeAbsolute(((String)args[i])));
					sb.append(IO.getText(filename));
				}catch(ClassCastException e){
					throw new TypeError(line, "Argument "+args[i]+" is not of class java.lang.String");
				}
			}
			return sb.toString();
		}
	};

	public final static NativeFunction wr = new NativeFunction("wr"){
		private static final long serialVersionUID = -4922864629656347638L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if(NativeFunction.argsMatch(line, args, String.class,String.class)){
				String filename = EnvironmentPaths.attachWDIfNecc(makeAbsolute(((String)args[1])));
				IO.safeWrite((String)args[0], filename);
			}else{
				typeError(line, args);
			}
			return EmbyNull.Null;
		}
	};

	public final static NativeFunction mkdir = new NativeFunction("mkdir"){
		private static final long serialVersionUID = -4922864629656347638L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if(NativeFunction.argsMatch(line, args, String.class)){
				String filename = EnvironmentPaths.attachWDIfNecc(makeAbsolute(((String)args[0])));
				File f = new File(filename);
				boolean b = f.mkdir();
				if(!b){
					System.err.println("Directory not created");
				}
				return filename;
			}else{
				typeError(line, args);
			}
			return EmbyNull.Null;
		}
	};

	public final static NativeFunction rmdir = new NativeFunction("rmdir"){
		private static final long serialVersionUID = -4922864629656347638L;
		@Override
		public Object execute(int line, Scope s, Object... args) {
			if(NativeFunction.argsMatch(line, args, String.class)){
				String filename = EnvironmentPaths.attachWDIfNecc(makeAbsolute(((String)args[0])));
				File f = new File(filename);
				if(! f.isDirectory()){
					System.err.println("not a directory");
					return EmbyNull.Null;
				}
				boolean b = f.delete();
				if(!b){
					System.err.println("Directory not deleted");
				}
				return filename;
			}else{
				typeError(line, args);
			}
			return EmbyNull.Null;
		}
	};
}
